Skip to content

test(gantt): anchor both contractEnvelope-6839 refusal arms on completion, and diagnose the six siblings - #8707

Merged
os-justin merged 2 commits into
mainfrom
claude/issue-8665-envelope-wait-siblings
Sep 9, 2026
Merged

test(gantt): anchor both contractEnvelope-6839 refusal arms on completion, and diagnose the six siblings#8707
os-justin merged 2 commits into
mainfrom
claude/issue-8665-envelope-wait-siblings

Conversation

@os-justin

Copy link
Copy Markdown
Collaborator

Part of #8665. That card asks for a component-level diagnosis of seven contractEnvelope-6839 siblings and names two as vacuous in the refusal direction. One of the two is; the other is not, and the probe says why.

What was measured, not read

⚠️ Every characterisation in #8665 is a reading of a wait EXPRESSION. Probed at the component, one of the two nominations does not hold.

The family shares an idiom, and plugin-gantt was the one file missing it

Eight of the nine DOM files settle the fetch explicitly before they read:

await find.mock.results[0].value;   // find's OWN answer, settled

plugin-gantt had zero occurrences. That single omission is what made it the measurably vacuous one.

plugin-gantt/ObjectGantt — REPAIRED (both sinks)

⭐ LEG 1, sink 2, with no bug reintroduced at all — the LIVE data envelope, rows answering immediately and the quick-filter domain one macrotask later:

shape what the arm read what the same mount settled to
old offersP3=false offered=[p1] offered=[p1,p2,p3]
new offersP3=true offered=[p1,p2,p3] offered=[p1,p2,p3]

The refusal assertion expect(offersUnloadedProject(view)).toBe(false) passed against a component that resolved the whole domain. It was measuring the polling tick, not the envelope. Same result at 50ms and at 300ms.

LEG 1, sink 1, uniform delay: the DOM at the old wait was the Loading Gantt chart... placeholder and the read threw Unable to find an element by: [data-testid="gantt-view"]. ⚠️ Reported honestly as a spurious red, not a silent pass — I could not construct a state where sink 1 passes vacuously, because gantt-view renders only below the if (loading) early return.

LEG 2 (new wait under the same forced ordering) and LEG 3 (ordinary ordering): every arm correct at 0ms / 50ms / 300ms, rows-late and domain-late alike.

Anchors chosen from the component's own mechanism

  • sink 1 waits for gantt-view. loading is cleared in reload's finally, in the same isCurrent()-guarded commit as setData, so its presence proves the row query settled AND committed.
  • sink 2 anchors on a control dimension. ObjectGantt publishes no DOM marker for the domain fetch on the refusing branch — a refused domain leaves lookupOptions[field] empty, the resolver falls through to "distinct values present in the loaded data", and the dropdown looks exactly as it does before the fetch answers. So the fixture adds a second lookup (owner) resolved by the same effect: setLookupOptions is called once, after the loop over every dimension, and no loaded task carries an owner, so its option can only come from the domain fetch having been read and committed.

Both anchors are PRESENCE assertions, so neither arm can pass by timing out.

Would an implementation strictly worse than the bug pass? Ablating extractRecords to "return nothing, ever":

before after
sink 2 refusal arm PASSED FAILS (Unable to find an element by: [data-testid="quick-filter-option-owner-u1"])

sink 1's refusal arm still reads 0 under that ablation; its positive arms refuse it. That is the whole family's documented design — all nine behave this way, the already-repaired plugin-tree included — so it is stated rather than claimed fixed.

plugin-dashboard/ObjectPivotTable — NOT repaired, premise falsified

The card says the wait queryByTestId('pivot') cannot separate "drawn empty" from "drawn before the data arrived". Measured, it can — and the anchor is a line the card's reading skipped, two lines above the wait it quotes.

With the records arm reintroduced ahead of data (the pre-fix bug), the refusal arm reddens at 0ms, 50ms and 300ms. It still reddens with ObjectPivotTable's loading-skeleton early return also disabled. Removing only the settle line, it goes vacuous exactly as predicted:

settle line delay arm reads component drew verdict
present 300ms 2 2 FAILS — discriminates
removed 50ms 0 2 PASSES — vacuous

⇒ the file already carries its completion anchor. Left alone deliberately.

The other five — diagnosed, not touched

All five carry the settle-line idiom, and in every one the waited-for node sits below a loading early return, so the wait is gated twice.

file component-side mechanism does the wait fail to gate the read? a repair would anchor on
plugin-calendar/ObjectCalendar loading starts true; if (loading) returns a placeholder, so the stubbed grid cannot mount early. Its comment calls the testid a mount signal — true and deliberate: it is envelope-INDEPENDENT, which is what lets one wait serve every arm. No. Gated by loading and by the settle line. Nothing needed. If hardened: assert the placeholder was observed, so the transition is two observations.
plugin-charts/ObjectChart ⭐ The card flags the lazy boundary. React.lazy lives in ChartRenderer.tsxwhich this pin vi.mocks away, so the pin never crosses it. The refusal arm asserts 'empty-state', a DIFFERENT node from the renderer, gated by if (loading && finalData.length === 0). No — and the kanban kinship does not transfer. Same shape of finding as tree-vs-kanban in PR #8664. Nothing for the wait. ⚠️ Separate weakness, different class: site 2's refusal asserts .not.toBe('Apollo'), which undefined satisfies — the objectui#8688 shape. Filed separately.
plugin-dashboard/ObjectDataTable Same two-branch shape PR #8664 replaced, but here both branches are presence-shaped and the refusal arm asserts 'empty-state' — a distinct node, not a count of 0. if (loading && finalData.length === 0) gates both. No. Nothing needed.
plugin-map/ObjectMap loading starts true; setLoading(false) commits with setData. Correct at 0/50/300ms with and without the settle line. No — but ⚠️ it is the only absence-shaped wait of the five, and at 0ms the Loading map... panel is never observed at all, so the wait is satisfied with no transition having occurred. It holds only because those two setStates commit together, and nothing pins that. Assert the panel was present first, or a positive post-settle node. The precondition, not the ordering.
plugin-timeline/ObjectTimeline if (loading && (!effectiveItems || effectiveItems.length === 0)) gates the renderer. No — but the wait says less than it looks: getByTestId throwing inside waitFor is what gates it; the data-item-count .not.toBeNull() half is inert, satisfied by '0'. The count half is decoration. A repair would name the renderer's mount and drop the attribute check, or assert the drawn shape.

⛔ No component was changed. Two component-layer findings are reported to the card, not fixed here.

Verification

  • pnpm exec vitest run packages/plugin-gantt/64 files, 502 tests, all passed.
  • tsc -p packages/plugin-gantt/tsconfig.test.json with the dependency closure built (pnpm --filter '@object-ui/plugin-gantt^...' build) — exit 0, TS2307 count 0 so the precondition was met, and --listFiles confirms the changed file is in the program.
  • eslint . in @object-ui/plugin-gantt — 92 files, 0 errors, exit 0. Warnings 362 (package-wide, pre-existing). The changed file went 6 → 9 @typescript-eslint/no-explicit-any warnings: three new helpers typed view: any, matching the convention every helper in that file already uses.
  • check:vi-mock-specifiers, check:vi-mock-inherit, check:control-bytes, check-changeset-presence, check-changeset-no-major — all exit 0.
  • Ablations mutated the READ SITE (extractRecords), never a pin, from a committed tree, with trap on EXIT/INT/TERM, absolute paths, on-disk proof in both directions (anchor counts AND git hash-object vs the HEAD blob) plus a line-total gate, and restore proven by state (git diff HEAD empty).
  • ⚠️ Two probe legs were VOID and are reported as void, not as readings: a first plugin-map probe omitted the pin's react-map-gl/maplibre mock and measured nothing, and a first pass leaked containers across runs. Both were rebuilt before any conclusion was drawn.

Changeset: empty frontmatter — test-only, releases nothing.

🤖 Generated with Claude Code

https://claude.ai/code/session_01YBWFb5YgMU5dw8p2VKj16S


Generated by Claude Code

…tion (objectui#8665)

Both refusal arms in `ObjectGantt.contractEnvelope-6839.test.tsx` read without
a completion anchor. Probed on the component with the row query and the
quick-filter domain query answering at different times:

- sink 2 (the option domain) asserted an ABSENCE with nothing proving the
  domain query had answered. With the LIVE `data` envelope and the domain
  answering one macrotask after the rows, the arm read
  `offersP3=false, offered=[p1]` while the same mount settled to
  `offered=[p1,p2,p3]`. The refusal assertion passed against a component that
  demonstrably resolved the whole domain.
- sink 1 read the bars straight after a wait on `find` merely having been
  CALLED. Under the same profile the DOM at that moment was the
  `Loading Gantt chart...` placeholder and the read threw
  `Unable to find an element by: [data-testid="gantt-view"]`.

sink 1 now waits for `gantt-view`, which renders below `ObjectGantt`'s
`if (loading)` early return; `loading` is cleared in `reload`'s `finally`, in
the same `isCurrent()`-guarded commit as `setData`, so its presence proves the
row query settled and committed.

sink 2 anchors on a CONTROL DIMENSION. The component publishes no DOM marker
for the domain fetch on the refusing branch, so the fixture adds a second
lookup (`owner`) resolved by the same effect: `setLookupOptions` is called once
after the loop over every dimension, and no loaded task carries an `owner`, so
its option can only come from the domain fetch having been read and committed.

Both anchors are PRESENCE assertions, so neither arm can pass by timing out.
The positive arms now also assert the drawn titles, not only a count.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YBWFb5YgMU5dw8p2VKj16S
@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 50 chunks) 3477.6 KB 3512.7 KB
Main entry chunk (gzip) 143.9 KB 350 KB
Entry file index-BkOatu4S.js
Status PASS

The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it.


📦 Bundle Size Report

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 15.67KB 5.75KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 25.05KB 9.16KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.18KB 10.59KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.65KB 2.22KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
auth (UserMenu.js) 3.41KB 1.23KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.21KB 10.80KB
auth (createAuthenticatedFetch.js) 8.46KB 3.43KB
auth (index.js) 3.19KB 1.44KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 26.08KB 7.56KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 499.42KB 114.32KB
core (index.js) 7.48KB 2.96KB
create-plugin (index.js) 10.12KB 3.28KB
data-objectstack (index.js) 198.39KB 55.29KB
fields (index.js) 244.36KB 61.61KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 1.22KB 0.64KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 6.57KB 2.76KB
i18n (index.js) 3.65KB 1.47KB
i18n (pickLocalized.js) 7.62KB 3.26KB
i18n (provider.js) 26.89KB 9.04KB
i18n (useDisplayLocale.js) 2.85KB 1.45KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 38.84KB 10.94KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 2.53KB 0.85KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 4.39KB 1.66KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.52KB 4.88KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.39KB 3.10KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 15.16KB 3.68KB
plugin-calendar (index.js) 49.00KB 13.91KB
plugin-charts (index.js) 71.39KB 19.92KB
plugin-chatbot (index.js) 194.53KB 46.34KB
plugin-dashboard (index.js) 131.43KB 34.44KB
plugin-designer (index.js) 215.51KB 44.29KB
plugin-detail (index.js) 251.25KB 65.00KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 131.01KB 32.32KB
plugin-gantt (index.js) 167.16KB 40.99KB
plugin-grid (index.js) 208.18KB 56.62KB
plugin-kanban (index.js) 55.44KB 15.73KB
plugin-list (index.js) 112.73KB 27.69KB
plugin-map (index.js) 20.49KB 6.83KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.42KB 11.92KB
plugin-timeline (index.js) 30.10KB 8.74KB
plugin-tree (index.js) 9.33KB 3.25KB
plugin-view (index.js) 84.54KB 20.84KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.66KB 3.50KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 81.07KB 26.86KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.63KB 2.18KB
react (schema-input.js) 2.32KB 1.24KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 6.58KB 2.74KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 5.55KB 2.45KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 20.57KB 5.88KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 13.64KB 4.59KB
types (ai.js) 0.20KB 0.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 1.00KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.93KB 1.49KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.25KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 4.73KB 2.28KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 14.27KB 5.47KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@os-justin
os-justin marked this pull request as ready for review September 8, 2026 23:54
@os-justin
os-justin enabled auto-merge September 8, 2026 23:54
@os-justin
os-justin added this pull request to the merge queue Sep 9, 2026
Merged via the queue into main with commit 8195905 Sep 9, 2026
35 checks passed
@os-justin
os-justin deleted the claude/issue-8665-envelope-wait-siblings branch September 9, 2026 01:12
os-justin pushed a commit that referenced this pull request Sep 9, 2026
… file

The merge before this one took this branch's side of an add/add conflict and
proved that drops no BYTE main carries. It does not prove it drops no CLAIM:
the header main landed makes five, and each needed a verdict against the AST
matcher that now sits underneath it. Four survived the rewrite. One had not.

RESTORED — the two regex modes' strict buckets are NESTED on this tree.
The rewrite kept "the modes are incomparable by construction" (M1) and dropped
the measurement that says the union of the two is just `path`'s bucket. M1 is
about shapes; on this tree ident-only is EMPTY, so a reader could take M1 as a
reason to run both modes and union them, which buys nothing. Re-measured on
both trees rather than copied forward:

    at da5e4f6            ident 15 ⊂ path 18   ident-only EMPTY, 3 path-only
    with main @ a9bc029   ident 12 ⊂ path 15   ident-only EMPTY, the SAME 3

and the same three `PermissionMatrixEditor` sites are the path-only ones in
both. The companion claim — that the mode choice was never the largest source
of movement — is restored as arithmetic the file can be re-run to check: the
mode separates THREE sites, D1 and D2 separate SIXTEEN (−7/+9, both verified
by diffing the site lists at da5e4f6).

ADDED — R2', the half of D1 that did NOT get repaired.
The window is scoped to the enclosing FUNCTION body. That kills the runaway
half (it can never reach the next `it`) and leaves the truncation half alive,
pointed the other way: a wait hosted in a helper the test awaits covers none of
the CALLER's statements. Forced on three probes — the same cross-recorder read
draws a flag with the wait inline and ZERO with it moved into a helper. Not a
corner: 503 of this tree's 3945 `await waitFor(...)` sites have their window
owned by an inner function, 464 of those a helper. The regex matcher was blind
here too, differently, so this is a surviving blind spot rather than a
regression — the header says which, and says the one shape where the old
matcher's runaway window did reach the read and this one does not.

The banner the reader running the script actually sees said the three error
sources were removed. Two were, plus half of the third; it now says that.

Also recorded: the counts move with the TREE alone. Merging main moved the
population 2776 → 2786 and every strict bucket down (AST 20 → 18, path 18 → 15,
ident 15 → 12) with the matcher byte-identical, because #8707/#8711/#8713 each
anchored a wait. That is claim 4 demonstrating itself.

Unchanged and re-verified: the census stays OUT of CI. No workflow and no
package script runs it; the only caller is the pin test, over six fixtures.

objectui#8704

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YBWFb5YgMU5dw8p2VKj16S
akarma-synetal pushed a commit to akarma-synetal/objectui that referenced this pull request Sep 9, 2026
… fixtures a home (objectstack-ai#8719)

* test(plugin-designer): anchor the lookupKeying absence pin on the handler's reload

`expect(deletes).toEqual([])` was dated to the first PUT by `waitFor(puts)`.
That covers the delete scan only because the scan happens to run before the
save loop in `handleObjectsChange` — a property of the page, not one the file
asserts. Anchor it on `reload()`, the handler's last statement, so the
emptiness is read after every write the handler makes.

Measured: with a stray `reset` issued after the saves, the `puts` wait leaves
this assertion green while C0/H1/H2 in the same file — which already anchor on
the reload — go red.

objectui#8690

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YBWFb5YgMU5dw8p2VKj16S

* chore(scripts): keep objectui#8690's recorder-wait census as a script

The detector answers where to look, never what is wrong, so it is a census
tool and NOT wired into CI: a gate on this list would institutionalise the
batch repair the card exists to prevent. Two recorder-matching modes, because
the choice moves the numbers, and the header records what each one measured on
the card's base commit rather than quoting the card's counts as reproduced.

Changeset declares an empty frontmatter: test-only plus one repo script,
nothing published moves.

objectui#8690

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YBWFb5YgMU5dw8p2VKj16S

* chore(scripts): record what the recorder-wait census's two matchers disagree about

The census script's numbers move with an internal choice its header stated but
never measured. objectui#8703 measured it, and the answer is wider than the
mode choice:

- The two `--recorder-match` modes are incomparable by construction — each
  flags a shape the other cannot see (forced on fixtures, both directions).
- On da5e4f6 the buckets are nevertheless nested: ident's 15 is a subset of
  path's 18, ident-only is empty. "They do not contain each other" is an
  in-principle claim, false as a measurement of this tree.
- The header's stated `path` blind spot (pushed bare, read as `host.inits[0]`)
  is wrong: both modes miss that shape.
- Two mode-independent rules dominate the mode choice: the forward window ends
  at the next `await` in the FILE rather than at the end of the enclosing test,
  and any textual occurrence counts as a read. Seven of the eighteen strict
  flags on that tree point at a declaration, a destructuring, a reset or a
  function parameter — not at a read.

Header rewritten to carry that, plus a caveat printed next to the counts. The
detector's behaviour is unchanged: both modes print byte-identical site lists.
Script and changeset only.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YBWFb5YgMU5dw8p2VKj16S

* chore(scripts): give the recorder-wait census an AST matcher, and its fixtures a home

objectui#8704. The census resolved a recorder by the SPELLING of its `.push(`
site, ended its forward window at the next textual `await` IN THE FILE, and
counted every textual occurrence as a read. Seven of the eighteen strict flags
it reported at da5e4f6 were therefore a declaration, a destructuring, a reset
or a function parameter — and the same truncation went blind on a genuine
cross-recorder read one ordinary `await` further on.

The default matcher is now an AST pass:

  - identity over bindings and aliases, not names, one hop through a helper's
    parameter and a same-file factory's return value;
  - windows scoped to the enclosing test body, in statements, ending at the next
    awaited SETTLING anchor rather than at any `await`;
  - every occurrence classified read / write / declaration.

The original census is kept behind `--matcher=regex`, so objectui#8690's and
objectui#8703's published numbers stay reproducible from this file rather than
being claims about a deleted script. Verified: at da5e4f6 it still prints
159 / 15 strict for `ident` and 167 / 18 strict for `path`, exactly as published.

objectui#8703's five throwaway fixtures are committed as the script's test suite,
with the sixth objectui#8704 asked for — the absence read objectui#8690 repaired,
which must STAY flagged, because "flag nothing" is strictly worse than the bug
and passes every other case. Both matchers are pinned over the same six files,
so the repair is pinned as a direction.

The header's "no count printed here is a corpus fact" caveat STAYS, with its
residuals restated: no type checker, a window rule that is a judgement rather
than a fact, and a flag that is still a site to read and not a defect. The
census remains out of CI; the pin test runs the matcher over the fixtures only.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YBWFb5YgMU5dw8p2VKj16S

* chore(scripts): resolve the census header claim by claim, not file by file

The merge before this one took this branch's side of an add/add conflict and
proved that drops no BYTE main carries. It does not prove it drops no CLAIM:
the header main landed makes five, and each needed a verdict against the AST
matcher that now sits underneath it. Four survived the rewrite. One had not.

RESTORED — the two regex modes' strict buckets are NESTED on this tree.
The rewrite kept "the modes are incomparable by construction" (M1) and dropped
the measurement that says the union of the two is just `path`'s bucket. M1 is
about shapes; on this tree ident-only is EMPTY, so a reader could take M1 as a
reason to run both modes and union them, which buys nothing. Re-measured on
both trees rather than copied forward:

    at da5e4f6            ident 15 ⊂ path 18   ident-only EMPTY, 3 path-only
    with main @ a9bc029   ident 12 ⊂ path 15   ident-only EMPTY, the SAME 3

and the same three `PermissionMatrixEditor` sites are the path-only ones in
both. The companion claim — that the mode choice was never the largest source
of movement — is restored as arithmetic the file can be re-run to check: the
mode separates THREE sites, D1 and D2 separate SIXTEEN (−7/+9, both verified
by diffing the site lists at da5e4f6).

ADDED — R2', the half of D1 that did NOT get repaired.
The window is scoped to the enclosing FUNCTION body. That kills the runaway
half (it can never reach the next `it`) and leaves the truncation half alive,
pointed the other way: a wait hosted in a helper the test awaits covers none of
the CALLER's statements. Forced on three probes — the same cross-recorder read
draws a flag with the wait inline and ZERO with it moved into a helper. Not a
corner: 503 of this tree's 3945 `await waitFor(...)` sites have their window
owned by an inner function, 464 of those a helper. The regex matcher was blind
here too, differently, so this is a surviving blind spot rather than a
regression — the header says which, and says the one shape where the old
matcher's runaway window did reach the read and this one does not.

The banner the reader running the script actually sees said the three error
sources were removed. Two were, plus half of the third; it now says that.

Also recorded: the counts move with the TREE alone. Merging main moved the
population 2776 → 2786 and every strict bucket down (AST 20 → 18, path 18 → 15,
ident 15 → 12) with the matcher byte-identical, because objectstack-ai#8707/objectstack-ai#8711/objectstack-ai#8713 each
anchored a wait. That is claim 4 demonstrating itself.

Unchanged and re-verified: the census stays OUT of CI. No workflow and no
package script runs it; the only caller is the pin test, over six fixtures.

objectui#8704

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YBWFb5YgMU5dw8p2VKj16S

---------

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants